#!/usr/bin/python3
# -*- coding:utf-8 -*-
# @who:     spacelacc
# @when:    2025/04/26
# @what:    Custom list views
# @vers:    V1


from PySide6.QtGui     import QFont, QFontMetricsF
from PySide6.QtGui     import QPainter, QColor, QPen, QAction
from PySide6.QtCore    import Qt, Signal, QPoint, QModelIndex
from PySide6.QtCore    import QAbstractListModel
from PySide6.QtWidgets import QListView, QSizePolicy, QStyle
from PySide6.QtWidgets import QStyledItemDelegate, QMenu

from customprobeparser import FileProbeParser
#from customprobeparser import BaseProbeParser


class CustomCinListModel(QAbstractListModel):
    def __init__(self, data:list=[]):
        super().__init__()
        self._data = data

    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    # ultimate data will be like this:
    # [1, '2411001', True, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, ...]
    # [2, '2411002', True, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, ...]
    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return self._data[index.row()][1]
        elif role == Qt.UserRole+1:
            return self._data[index.row()][1]
        elif role == Qt.UserRole+2:
            return self._data[index.row()][2]
        else:
            return None

    def list_model_data_get(self):
        return self._data

    def list_model_data_append(self, data:list):
        ncnt = len(self._data)
        self.beginInsertRows(QModelIndex(), ncnt, ncnt)
        self._data.extend(data)
        self.endInsertRows()

    def list_model_data_update(self, data:list):
        ncnts = len(self._data)
        nrows = len(data)
        self.beginInsertRows(QModelIndex(), ncnts, ncnts+nrows-1)
        self._data.extend(data)
        self.endInsertRows()

    def list_model_data_reset(self):
        self.beginResetModel()
        self._data.clear()
        self.endResetModel()


class CustomCinListDelegate(QStyledItemDelegate):
    def __init__(self):
        super().__init__()
        self.bgColorUsual = QColor(255, 255, 255)
        self.bgColorHover = QColor(0, 143, 191)
        self.bgColorClick = QColor(255, 110, 170)
        
        self.itemMouseHover = -1
        self.itemMouseClick = -1

    def listMouseHoverHandler(self, row):
        self.itemMouseHover = row

    def listMouseClickHandler(self, row):
        self.itemMouseClick = row

    def listMouseLeaveHandler(self):
        self.itemMouseHover = -1

    def paint(self, painter, option, index):
        painter.save()
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
        painter.setPen(QPen(Qt.black, 1))

        width   = option.rect.width()
        height  = option.rect.height()

        metric = QFontMetricsF(option.font)
        ldit = '  >>> '
        rdit = ' <<<  '
        lblt = index.data(Qt.UserRole+1)
        ldiw = metric.horizontalAdvance(ldit)
        rdiw = metric.horizontalAdvance(rdit)
        lblw = metric.horizontalAdvance(lblt)
        icow = 18
        sepw = (width - ldiw - rdiw - lblw - icow) / 2.0
        ldirect = option.rect.adjusted(0, 0, sepw+icow+lblw+sepw+rdiw, 0)
        icorect = option.rect.adjusted(ldiw+sepw, 0, lblw+rdiw+sepw, 0)
        lblrect = option.rect.adjusted(ldiw+sepw+icow, 0, sepw+rdiw, 0)
        rdirect = option.rect.adjusted(ldiw+sepw+icow+lblw+sepw, 0, 0, 0)

        # Draw background
        if index.row() == self.itemMouseHover:
            painter.fillRect(option.rect, self.bgColorHover)
            painter.drawText(ldirect, Qt.AlignLeft|Qt.AlignVCenter, ldit)
            painter.drawText(rdirect, Qt.AlignLeft|Qt.AlignVCenter, rdit)
        else:
            painter.fillRect(option.rect, self.bgColorUsual)

        if index.row() == self.itemMouseClick:
            # Draw border
            rect = option.rect
            painter.fillRect(rect, self.bgColorClick)
            idtrectl = rect.adjusted(0, 0, -(width-4), 0)
            idtrectr = rect.adjusted(width-4, 0, 0, 0)
            painter.fillRect(idtrectl, Qt.black)
            painter.fillRect(idtrectr, Qt.black)
            painter.drawText(ldirect, Qt.AlignLeft|Qt.AlignVCenter, ldit)
            painter.drawText(rdirect, Qt.AlignLeft|Qt.AlignVCenter, rdit)

        # Draw Cube
        h = 0.8 * option.rect.height()
        w = 0.7 * icow
        cw = option.rect.left() + ldiw + sepw + 0.4*icow
        ch = option.rect.top() + 0.5 * option.rect.height()
        painter.setPen(QPen(Qt.black, 2))
        painter.drawRect(cw-0.5*w, ch-h/6, 2*w/3, 2*h/3)
        point1 = QPoint(cw-0.5*w, ch-h/6)
        point2 = QPoint(cw-w/6, ch-0.5*h)
        point3 = QPoint(cw+0.5*w, ch-0.5*h)
        point4 = QPoint(cw+w/6, ch-h/6)
        painter.drawPolyline([point1, point2, point3, point4])
        point1 = QPoint(cw+0.5*w, ch-0.5*h)
        point2 = QPoint(cw+0.5*w, ch+h/6)
        point3 = QPoint(cw+w/6, ch+0.5*h)
        painter.drawPolyline([point1, point2, point3])
        painter.drawEllipse(QPoint(cw-w/6, ch+h/6), 1, 1)
        
        # Draw Text
        painter.drawText(lblrect, Qt.AlignLeft|Qt.AlignVCenter, lblt)
        painter.restore()


# List used for 'Parader': 'stdin'
class CustomCinListView(QListView):
    signalMouseHover = Signal(int)
    signalMouseLeave = Signal()
    signalMouseClick = Signal(int)
    signalItemClicked = Signal(QModelIndex)

    def __init__(self, width, height, adapter, parent=None):
        super().__init__(parent)
        self.mww = width     # Main Window Width
        self.mwh = height    # Main Window Height
        self.adapter = adapter
        self.updates = 0
        self.assembler = []
        self.collects = CustomCinListModel(self.assembler)
        self.delegate = CustomCinListDelegate()
        self.setModel(self.collects)
        self.setItemDelegate(self.delegate)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.setMouseTracking(True)

        self.list_view_context_setup()
        self.setContextMenuPolicy(Qt.CustomContextMenu)
#       self.customContextMenuRequested.connect(self.list_view_context_popup)
        self.customContextMenuRequested.connect(self._popup_context)
        self.signalMouseHover.connect(self.delegate.listMouseHoverHandler)
        self.signalMouseClick.connect(self.delegate.listMouseClickHandler)
        self.signalMouseLeave.connect(self.delegate.listMouseLeaveHandler)

    def list_view_context_setup(self):
        self.context = QMenu(self)
        self.context.setEnabled(False)   # Originally not used
        spreadsheet_data = QAction('从数据表导入数据', self)
        database_data = QAction('从数据库导入数据', self)
        spreadsheet_filter = QAction('过滤数据表导入数据', self)
        database_filter = QAction('过滤数据库导入数据', self)
        clear_list_data = QAction('清空列表所有数据', self)
        parse_spreadsheet = QAction('解析数据表至数据库', self)

        self.context.addAction(spreadsheet_data)
        self.context.addAction(database_data)
        self.context.addSeparator()
        self.context.addAction(spreadsheet_filter)
        self.context.addAction(database_filter)
        self.context.addSeparator()
        self.context.addAction(clear_list_data)
        self.context.addSeparator()
        self.context.addAction(parse_spreadsheet)

        spreadsheet_data.triggered.connect(self.data_from_spreadsheet)
        database_data.triggered.connect(self.data_from_database)
        spreadsheet_filter.triggered.connect(self.filter_spreadsheet_data)
        database_filter.triggered.connect(self.filter_database_data)
        clear_list_data.triggered.connect(self.clear_list_data)
        parse_spreadsheet.triggered.connect(self.parse_spreadsheet_into_database)

#   def list_view_context_popup(self, pos):
    def _popup_context(self, pos):
        if self.context.isEnabled():
            self.context.exec(self.mapToGlobal(pos))

    def list_view_stdin_enable_context(self):
        self.context.setEnabled(True)

    def list_view_stdin_disable_context(self):
        self.context.setEnabled(False)
        
    def list_view_stdin_data(self):
        return self.collects.list_model_data_get()

    def list_view_stdin_reset(self):
        self.collects.list_model_data_reset()

    def list_view_stdin_update(self, data:list):
        if not data:
            return
        self.collects.list_model_data_update(data)
        self.updates = len(data)

    def list_view_stdin_update_count(self):
        return self.updates

    def data_from_spreadsheet(self):
        dialog = FileProbeParser()
        dialog.resize(0.9*self.mww, 0.9*self.mwh)
        dialog.signalDataDone.connect(self.list_view_stdin_update)
        retvalue = dialog.exec()

    def data_from_database(self):
        print("import data from database")
    
    def filter_spreadsheet_data(self):
        print("filter data from spreadsheet")

    def filter_database_data(self):
        print("filter data from database")

    def clear_list_data(self):
        self.assembler.clear()
        self.collects.list_model_data_reset()

    def parse_spreadsheet_into_database(self):
        print("parse spreadsheet into database")

    def leaveEvent(self, event):
        self.signalMouseLeave.emit()

    def mouseMoveEvent(self, event):
        index = self.indexAt(event.position().toPoint())
        self.signalMouseHover.emit(index.row())
        start = self.collects.index(0)
        stop  = self.collects.index(self.collects.rowCount())
        self.collects.dataChanged.emit(start, stop)
        super().mouseMoveEvent(event)

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            clicked = self.indexAt(event.position().toPoint())
            self.signalMouseClick.emit(clicked.row())
            self.collects.dataChanged.emit(clicked, clicked)
            self.signalItemClicked.emit(clicked)
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            clicked = self.indexAt(event.position().toPoint())
            self.collects.dataChanged.emit(clicked, clicked)
        super().mouseReleaseEvent(event)

#app.setStyleSheet("""
#        QListView {
#            border: none;
#        }
#        QListView::item {
#            height: 32px;
#        }
#""")
#data = [
#    [1, '2411001', True,  1, 2, 3, 4, 5, 6, 7, 8],
#    [2, '2411002', False, 1, 2, 3, 4, 5, 6, 7, 8],
#    [3, '2411003', True,  1, 2, 3, 4, 5, 6, 7, 8],
#    [4, '2411004', False, 1, 2, 3, 4, 5, 6, 7, 8],
#]
#app = QApplication()
#win = CustomCoreinListView()
#win.list_view_update(data)
#win.resize(750, 350)
#win.show()
#app.exec()




class CustomCoutListModel(QAbstractListModel):
    def __init__(self, data:list=[]):
        super().__init__()
        self._data = data

    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    # data will be like this:
    # [0, [1, '2411001', True, ...], [3, '2411002', True, ...]]
    # [1, [6, '2412001', True, ...], [9, '2412002', True, ...]]
    # [2, [7, '2410001', True, ...], [8, '2410002', True, ...]]
    def data(self, index, role=Qt.DisplayRole):
        nth = index.row()
        if role == Qt.DisplayRole:
            p1 = self._data[nth][1]
            p2 = self._data[nth][2]
            if self._data[nth][0] == 0:    # 0, Fully Paired
                output = "[ {}, {} ]".format(p1[1], p2[1])
            elif self._data[nth][0] == 1:  # 1, Fuzzy Paired
                output = "( {}, {} )".format(p1[1], p2[1])
            else:                          # 2, Fails Paired
                output = p1[1]
            return output
        elif role == Qt.UserRole+1:        # kind of pairer
            return self._data[nth][0]
        elif role == Qt.UserRole+2:        # probes to plot
            return self._data[nth][1:]

        elif role == Qt.UserRole+3:        # left probe of pair
            return self._data[nth][1][1]
        elif role == Qt.UserRole+4:        # right probe of pair
            return self._data[nth][2][1]
        else:
            return None

    def list_model_data_renew(self, data:list):
        self.beginResetModel()
        self._data.clear()
        self._data.extend(data)
        self.endResetModel()

    def list_model_data_reset(self):
        self.beginResetModel()
        self._data.clear()
        self.endResetModel()

    def list_model_data_update(self, data:list):
        ncnts = len(self._data)
        nrows = len(data)
        self.beginInsertRows(QModelIndex(), ncnts, ncnts+nrows-1)
        self._data.extend(data)
        self.endInsertRows()


class CustomCoutListDelegate(QStyledItemDelegate):
    def __init__(self):
        super().__init__()
        self.bgColorUsual = QColor(255, 255, 255)
        self.bgColorHover = QColor(0, 143, 191)
        self.bgColorClick = QColor(255, 110, 170)
        
        self.itemMouseHover = -1
        self.itemMouseClick = -1

    def listMouseHoverHandler(self, row):
        self.itemMouseHover = row

    def listMouseClickHandler(self, row):
        self.itemMouseClick = row

    def listMouseLeaveHandler(self):
        self.itemMouseHover = -1

    def _draw_cube_forward(self, painter, w, h, cw, ch):
        painter.drawRect(cw-0.5*w, ch-h/6, 2*w/3, 2*h/3)
        point1 = QPoint(cw-0.5*w, ch-h/6)
        point2 = QPoint(cw-w/6, ch-0.5*h)
        point3 = QPoint(cw+0.5*w, ch-0.5*h)
        point4 = QPoint(cw+w/6, ch-h/6)
        painter.drawPolyline([point1, point2, point3, point4])
        point1 = QPoint(cw+0.5*w, ch-0.5*h)
        point2 = QPoint(cw+0.5*w, ch+h/6)
        point3 = QPoint(cw+w/6, ch+0.5*h)
        painter.drawPolyline([point1, point2, point3])
        painter.drawEllipse(QPoint(cw-w/6-1, ch+h/6), 1, 1)

    def _draw_cube_reverse(self, painter, w, h, cw, ch):
        painter.drawRect(cw-w/6, ch-h/6, 2*w/3, 2*h/3)
        point1 = QPoint(cw-w/6, ch-h/6)
        point2 = QPoint(cw-0.5*w, ch-0.5*h)
        point3 = QPoint(cw-0.5*w, ch+h/6)
        point4 = QPoint(cw-w/6, ch+0.5*h)
        painter.drawPolyline([point1, point2, point3, point4])
        point1 = QPoint(cw-0.5*w, ch-0.5*h)
        point2 = QPoint(cw+w/6, ch-0.5*h)
        point3 = QPoint(cw+0.5*w, ch-h/6)
        painter.drawPolyline([point1, point2, point3])
        painter.drawEllipse(QPoint(cw+w/6, ch+h/6), 1, 1)

    def _draw_perfect_paired(self, painter, ox, oy, w, h):
        point1 = QPoint(ox, oy+3.5)
        point2 = QPoint(ox+w, oy+3.5)
        painter.drawLine(point1, point2)
        point1 = QPoint(ox, oy+0.5*h+2.0)
        point2 = QPoint(ox+w, oy+0.5*h+2.0)
        painter.drawLine(point1, point2)
        point1 = QPoint(ox, oy+h+0.5)
        point2 = QPoint(ox+w, oy+h+0.5)
        painter.drawLine(point1, point2)

    def _draw_partial_paired(self, painter, ox, oy, w, h):
        point1 = QPoint(ox, oy+3.5)
        point2 = QPoint(ox+0.35*w, oy+3.5)
        painter.drawLine(point1, point2)
        point1 = QPoint(ox+0.65*w, oy+3.5)
        point2 = QPoint(ox+w, oy+3.5)
        painter.drawLine(point1, point2)

        point1 = QPoint(ox, oy+0.5*h+2.0)
        point2 = QPoint(ox+0.35*w, oy+0.5*h+2.0)
        painter.drawLine(point1, point2)
        point1 = QPoint(ox+0.65*w, oy+0.5*h+2.0)
        point2 = QPoint(ox+w, oy+0.5*h+2.0)
        painter.drawLine(point1, point2)

        point1 = QPoint(ox, oy+h+0.5)
        point2 = QPoint(ox+0.35*w, oy+h+0.5)
        painter.drawLine(point1, point2)
        point1 = QPoint(ox+0.65*w, oy+h+0.5)
        point2 = QPoint(ox+w, oy+h+0.5)
        painter.drawLine(point1, point2)


    def paint(self, painter, option, index):
        painter.save()
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
        painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
        # (1). Processing background-color
        if option.state & QStyle.State_Selected:
            painter.fillRect(option.rect, option.palette.highlight())
        elif option.state & QStyle.State_MouseOver:
            painter.fillRect(option.rect, option.palette.highlight())
        else:
            painter.fillRect(option.rect, option.palette.base())

        metric = QFontMetricsF(option.font)
        ldit = ' >> '                     # left line indicator
        rdit = ' << '                     # right line indicator
        lprt = index.data(Qt.UserRole+3)  # left probe serial
        rprt = index.data(Qt.UserRole+4)  # right probe serial

        # width in pixels of every part
        width = option.rect.width()
        ldiw = metric.horizontalAdvance(ldit)
        rdiw = metric.horizontalAdvance(rdit)
        lprw = metric.horizontalAdvance(lprt)
        rprw = metric.horizontalAdvance(rprt)
        symw = 36                         # connect symbol width
        icow = 18
        sepw = (width - ldiw - rdiw - lprw - rprw - symw - 2*icow) / 2.0

        # drawing rect regions of every part
        ldir = option.rect.adjusted(0, 0, 2*sepw+2*icow+lprw+symw+rprw+rdiw, 0)
        lprr = option.rect.adjusted(ldiw+sepw+icow, 0, symw+rprw+icow+sepw+rdiw, 0)
        symr = option.rect.adjusted(ldiw+sepw+icow+lprw, 0, rprw+icow+sepw+rdiw, 0)
        rprr = option.rect.adjusted(ldiw+sepw+icow+lprw+symw, 0, icow+sepw+rdiw, 0)
        rdir = option.rect.adjusted(2*sepw+2*icow+lprw+symw+rprw+ldiw, 0, 0, 0)
        
        # Draw background
        if index.row() == self.itemMouseHover:
            painter.fillRect(option.rect, self.bgColorHover)
#           painter.drawText(ldir, Qt.AlignLeft|Qt.AlignVCenter, ldit)
#           painter.drawText(rdir, Qt.AlignLeft|Qt.AlignVCenter, rdit)
        elif index.row() == self.itemMouseClick:
            painter.fillRect(option.rect, self.bgColorClick)
        else:
            painter.fillRect(option.rect, self.bgColorUsual)

        # Draw Connection
        h = 0.8 * option.rect.height()
        ox = option.rect.left()+ldiw+sepw+icow+lprw+0.1*symw
        oy = option.rect.top() + 0.2*h
        if index.data(Qt.UserRole+1) == 0:
            painter.setPen(QPen(QColor(34, 140, 34), 2))
            self._draw_perfect_paired(painter, ox, oy, 0.8*symw, 0.6*h)
        elif index.data(Qt.UserRole+1) == 1:
            painter.setPen(QPen(QColor(255, 153, 0), 2))
            self._draw_partial_paired(painter, ox, oy, 0.8*symw, 0.6*h)
        else:
#           painter.setPen(QPen(QColor(255, 69, 0), 2))
            painter.setPen(QPen(Qt.black, 2))
        # Draw Cubes
        w = 0.7 * icow
        cwl = option.rect.left()+ldiw+sepw+0.4*icow
        cwr = option.rect.left()+ldiw+sepw+icow+lprw+symw+rprw+0.6*icow
        ch = option.rect.top() + 0.5 * option.rect.height()
        self._draw_cube_forward(painter, w, h, cwl, ch)
        self._draw_cube_reverse(painter, w, h, cwr, ch)

        # Draw Hover Indicator
        if index.row() == self.itemMouseHover:
            painter.drawText(ldir, Qt.AlignLeft|Qt.AlignVCenter, ldit)
            painter.drawText(rdir, Qt.AlignLeft|Qt.AlignVCenter, rdit)
        # Draw Click Indicator
        if index.row() == self.itemMouseClick:
            idtlrect = option.rect.adjusted(0, 0, -(width-4), 0)
            idtrrect = option.rect.adjusted(width-4, 0, 0, 0)
            painter.fillRect(idtlrect, painter.pen().color())
            painter.fillRect(idtrrect, painter.pen().color())


        # Draw Serials
        painter.setPen(QPen(Qt.black, 2))
        painter.drawText(lprr, Qt.AlignLeft|Qt.AlignVCenter, lprt)
        painter.drawText(rprr, Qt.AlignLeft|Qt.AlignVCenter, rprt)
        painter.restore()


class CustomCoutListView(QListView):
    signalProbePlot = Signal(list)
    signalMouseHover = Signal(int)
    signalMouseLeave = Signal()
    signalMouseClick = Signal(int)

    def __init__(self, width, height, adapter):
        super().__init__()
        self.mww = width
        self.mwh = height
        self.adapter = adapter
        self.collects = CustomCoutListModel()
        self.delegate = CustomCoutListDelegate()
        self.setModel(self.collects)
        self.setItemDelegate(self.delegate)
        self.setMouseTracking(True)

        self.signalMouseHover.connect(self.delegate.listMouseHoverHandler)
        self.signalMouseClick.connect(self.delegate.listMouseClickHandler)
        self.signalMouseLeave.connect(self.delegate.listMouseLeaveHandler)

#   def list_view_update(self, data:list):
    def list_view_stdout_update(self, data:list):
        self.collects.list_model_data_update(data)

#   def list_view_reset(self):
    def list_view_stdout_reset(self):
        self.collects.list_model_data_reset()

#   def list_view_renew(self, data:list):
    def list_view_stdout_renew(self, data:list):
        self.collects.list_model_data_renew(data)

    def prev(self):
        current = self.currentIndex().row()
        current -= 1
        if current < 0:
            return
        index = self.collects.index(current, 0)
        self.setCurrentIndex(index)
        probes = index.data(Qt.UserRole+2)
        if probes[0] == probes[1]:
            del probes[1]
        self.signalProbePlot.emit(probes)

    def next(self):
        current = self.currentIndex().row()
        current += 1
        if current == self.collects.rowCount():
            return
        index = self.collects.index(current, 0)
        self.setCurrentIndex(index)
        probes = index.data(Qt.UserRole+2)
        if probes[0] == probes[1]:
            del probes[1]
        self.signalProbePlot.emit(probes)


    def leaveEvent(self, event):
        self.signalMouseLeave.emit()

    def mouseMoveEvent(self, event):
        index = self.indexAt(event.position().toPoint())
        if not index.isValid():
            return
        self.signalMouseHover.emit(index.row())
        start = self.collects.index(0)
        stop  = self.collects.index(self.collects.rowCount())
        self.collects.dataChanged.emit(start, stop)
        super().mouseMoveEvent(event)

    def mousePressEvent(self, event):
        if event.buttons() == Qt.MouseButton.LeftButton:
            clicked = self.indexAt(event.position().toPoint())
            self.signalMouseClick.emit(clicked.row())
            self.collects.dataChanged.emit(clicked, clicked)
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            clicked = self.indexAt(event.position().toPoint())
            self.collects.dataChanged.emit(clicked, clicked)
        super().mouseReleaseEvent(event)

    def mouseDoubleClickEvent(self, event):
        clicked = self.indexAt(event.position().toPoint())
        if not clicked.isValid():
            return

        probes = clicked.data(Qt.UserRole+2)
        # Remove duplicated probe if there is any
        if probes[0] == probes[1]:
            del probes[1]

        # Emit signal to notify 'plotter' plotting data
        self.signalProbePlot.emit(probes)
        super().mouseDoubleClickEvent(event)
        
